package cn.deepmax.spring.cloud.common.web;

import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Consumer;

/**
 * 支持  ArrayQuery 注解
 */
@Slf4j
public class ArrayQuerySupport {

    public static final String SEP = ",";

    public static void transform(ArrayQuery query){
        if(query==null) return;
        val map = query.transformMap();
        if(map==null||map.isEmpty()) return;
        Map<String, PropertyDescriptor> propertyDescriptorMap = new HashMap<>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(query.getClass());
            for(PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()){
                propertyDescriptorMap.put(descriptor.getName(), descriptor);
            }
        } catch (IntrospectionException e) {
            log.warn("Failed to introspect query valueOf type "+query.getClass().getName(), e);
            return;
        }
        for(Map.Entry<String,String> entry : map.entrySet()){
            val source = propertyDescriptorMap.get(entry.getKey());
            val target = propertyDescriptorMap.get(entry.getValue());
            if(source!=null && target!=null){
                try {
                    doFieldTransform(query, source, target);
                } catch (InvocationTargetException |IllegalAccessException | NoSuchFieldException e) {
                    //ignore
                }
            }else{
                log.warn("source[{}] or target[{}] not found in class[{}]. check your transformMap.", entry.getKey(), entry.getValue(),query.getClass().getName());
            }
        }
    }

    private static void doFieldTransform(ArrayQuery query, PropertyDescriptor sourceP, PropertyDescriptor targetP) throws InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Object sourceValue = sourceP.getReadMethod().invoke(query);
        if(sourceValue!=null && !(sourceValue instanceof String)){
            throw new UnsupportedOperationException("ListQuery class's field["+sourceP.getName()+"] should be type valueOf String");
        }
        String fieldName = targetP.getName();
        Field targetF = getField(query.getClass(), fieldName);
        val t = targetF.getGenericType();
        if(!(t instanceof ParameterizedType)){
            throw new UnsupportedOperationException("ListQuery class's field["+targetP.getName()+"] should be type valueOf ParameterizedType(List or Set)");
        }
        ParameterizedType fieldType = (ParameterizedType)t;
        Type[] actualTypeArguments = fieldType.getActualTypeArguments();
        if(actualTypeArguments==null || actualTypeArguments.length!=1){
            throw new UnsupportedOperationException("ListQuery class's field["+targetP.getName()+"] should be type valueOf ParameterizedType(List or Set), actualTypeArguments should have length valueOf 1.");
        }
        if(fieldType.getRawType().getTypeName().equals("java.util.List")){
            createList(query, (String)sourceValue, actualTypeArguments[0], targetP.getWriteMethod());
        }else if(fieldType.getRawType().getTypeName().equals("java.util.Set")){
            createSet(query, (String)sourceValue, actualTypeArguments[0], targetP.getWriteMethod());
        }else{
            throw new UnsupportedOperationException("ListQuery class's field["+targetP.getName()+"] should be type valueOf List or Set");
        }

    }

    @SuppressWarnings("unchecked")
    private static void createList(ArrayQuery query, String source, Type type, Method writer) throws InvocationTargetException, IllegalAccessException {
        List result = new ArrayList();
        doCreate(query.getClass().getName()+writer.getName(), source, type,it->result.add(it));
        writer.invoke(query, result);
    }

    @SuppressWarnings("unchecked")
    private static void createSet(ArrayQuery query, String source, Type type, Method writer) throws InvocationTargetException, IllegalAccessException {
        Set result = new HashSet();
        doCreate(query.getClass().getName()+writer.getName(), source, type,it->result.add(it));
        writer.invoke(query, result);
    }


    @SuppressWarnings("unchecked")
    private static void doCreate(String msg, String source, Type type, Consumer consumer){
        if(source==null||source.length()==0){
            return;
        }
        val s = source.split(SEP);
        for(String it:s){
            if(it!=null&&it.length()>0){
                if(type.getTypeName().equals("java.lang.String")){
                    consumer.accept(it);
                }else if(type.getTypeName().equals("java.lang.Long")){
                    consumer.accept(Long.parseLong(it));
                }else if(type.getTypeName().equals("java.lang.Integer")){
                    consumer.accept(Integer.parseInt(it));
                }else{
                    throw new UnsupportedOperationException("In class " + msg +" Type should be String/Long/Integer");
                }
            }
        }
    }


    private static Field getField(Class<?> clazz, String fieldName) {
        Field target = null;
        while(clazz!=null){
            try {
                target = clazz.getDeclaredField(fieldName);
                return target;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        return target;
    }
}
